package logger

import (
	"encoding/json"
	"fmt"
	"gin-web/app/core/config"
	"gin-web/app/utils/env"
	file2 "gin-web/app/utils/file"
	"github.com/bwmarrin/snowflake"
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"time"
)

// 日志输出时间格式模板
const (
	logTimeTpl = "2006-01-02T15:04:05.000Z07:00"
	LogId      = "X-Request-Log-Id"
)

type consoleColorModeValue int

const (
	autoColor consoleColorModeValue = iota
	disableColor
	forceColor
)

const (
	green   = "\033[97;42m"
	white   = "\033[90;47m"
	yellow  = "\033[90;43m"
	red     = "\033[97;41m"
	blue    = "\033[97;44m"
	magenta = "\033[97;45m"
	cyan    = "\033[97;46m"
	reset   = "\033[0m"
)

var consoleColorMode = autoColor

type LogFormatter func(params LogFormatterParams) string

type LogFormatterParams struct {
	Hostname string
	LocalIp  string
	Format   string
	LogId    string
	Request  *http.Request

	// TimeStamp shows the time after the server returns a response.
	TimeStamp time.Time
	// StatusCode is HTTP response code.
	StatusCode int
	// Latency is how much time the server cost to process a certain request.
	Latency time.Duration
	// ClientIP equals Context's ClientIP method.
	ClientIP string
	// Method is the HTTP method given to the request.
	Method string
	// Path is a path the client requests.
	Path string
	// ErrorMessage is set if error has occurred in processing the request.
	ErrorMessage string
	// isTerm shows whether gin's output descriptor refers to a terminal.
	isTerm bool
	// BodySize is the size of the Response Body
	BodySize int
	// Keys are the keys set on the request's context.
	Keys map[string]any
}

// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
func (p *LogFormatterParams) StatusCodeColor() string {
	code := p.StatusCode

	switch {
	case code >= http.StatusContinue && code < http.StatusOK:
		return white
	case code >= http.StatusOK && code < http.StatusMultipleChoices:
		return green
	case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
		return white
	case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
		return yellow
	default:
		return red
	}
}

// MethodColor is the ANSI color for appropriately logging http method to a terminal.
func (p *LogFormatterParams) MethodColor() string {
	method := p.Method

	switch method {
	case http.MethodGet:
		return blue
	case http.MethodPost:
		return cyan
	case http.MethodPut:
		return yellow
	case http.MethodDelete:
		return red
	case http.MethodPatch:
		return green
	case http.MethodHead:
		return magenta
	case http.MethodOptions:
		return white
	default:
		return reset
	}
}

// ResetColor resets all escape attributes.
func (p *LogFormatterParams) ResetColor() string {
	return reset
}

var defaultLogFormatter = func(param LogFormatterParams) string {
	//支持json text
	if param.Format == "json" {
		//json
		var rel = map[string]any{
			"ip":         param.ClientIP,
			"log_id":     param.LogId,
			"time":       param.TimeStamp.Format(logTimeTpl),
			"latency":    param.Latency.Milliseconds(),
			"method":     param.Method,
			"path":       param.Path,
			"query":      param.Request.URL.RawQuery,
			"status":     param.StatusCode,
			"error":      param.ErrorMessage,
			"size":       param.BodySize,
			"local_ip":   param.LocalIp,
			"hostname":   param.Hostname,
			"user_agent": param.Request.UserAgent(),
			"referer":    param.Request.Referer(),
		}

		b, _ := json.Marshal(rel)
		return string(b)
	} else {
		//文本
	}

	var statusColor, methodColor, resetColor string
	if param.isTerm {
		statusColor = param.StatusCodeColor()
		methodColor = param.MethodColor()
		resetColor = param.ResetColor()
	}

	if param.Latency > time.Minute {
		param.Latency = param.Latency.Truncate(time.Second)
	}
	return fmt.Sprintf("[%s]\t%s\t%s\t%d\t%s%s%s\t%s\t%s\t%s%d%s\t%s\t%d\t%s\t%s\t%s\n",
		param.ClientIP,
		param.LogId,
		param.TimeStamp.Format(logTimeTpl),
		param.Latency.Milliseconds(),
		methodColor, param.Method, resetColor,
		param.Path,
		param.Request.URL.RawQuery,
		statusColor, param.StatusCode, resetColor,
		param.ErrorMessage,
		param.BodySize,
		param.Hostname,
		param.Request.UserAgent(),
		param.Request.Referer(),
	)
}

func New(cfg config.ServiceLog, prjHome string) gin.HandlerFunc {

	var err error
	var output *os.File
	//解决输出位置
	switch cfg.Output {
	case "file":
		//输出到文件
		filePath := cfg.LogPath
		if filePath == "" {
			filePath = "logs/access.log"
		}
		output, err = file2.GetFilePointer(filePath, prjHome)
		if err != nil {
			panic(err)
		}
	default:
		output = os.Stdout
	}

	localIP := env.LocalIP()
	localHostname := env.LocalHostname()

	return func(c *gin.Context) {
		//生成请求id
		var logId = c.GetHeader(LogId)
		if logId == "" {
			//使用雪花算法生成请求id
			node, _ := snowflake.NewNode(1)
			logId = node.Generate().String()
		}

		if cfg.LogIdShowHeader {
			//将logId输出
			c.Header(LogId, logId)
		}

		start := time.Now()
		path := c.Request.URL.Path

		c.Next()

		//定义不需要日志的变量
		var skip map[string]struct{}
		if length := len(cfg.SkipPaths); length > 0 {
			skip = map[string]struct{}{}
			for _, path := range cfg.SkipPaths {
				skip[path] = struct{}{}
			}
		}

		//判断是否需要跳过
		if _, ok := skip[path]; ok {
			//跳过
			return
		}
		var param = LogFormatterParams{
			Request:      c.Request,
			Keys:         c.Keys,
			Format:       cfg.LogFormat,
			LocalIp:      localIP,
			Hostname:     localHostname,
			TimeStamp:    time.Now(),
			LogId:        logId,
			ClientIP:     c.ClientIP(),
			Method:       c.Request.Method,
			StatusCode:   c.Writer.Status(),
			ErrorMessage: c.Errors.ByType(gin.ErrorTypePrivate).String(),
			BodySize:     c.Writer.Size(),
			Path:         path,
		}
		param.Latency = param.TimeStamp.Sub(start)
		if cfg.Output != "file" {
			//输出到控制台，需要加载颜色
			param.isTerm = true
		}
		_, _ = fmt.Fprint(output, defaultLogFormatter(param))
	}
}
